/* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements.  See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License.  You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/


/*************************************************************************
 * File: python_codec.cpp
 *************************************************************************
 *\author: starlove
 *************************************************************************/

#include <codec/python_codec.h>
#include <platform/debug.h>

LPIEncoder CPythonCodec::GetEncoder()
{
    return &m_encoder;
}

LPIDecoder CPythonCodec::GetDecoder()
{
    return &m_decoder;
}

bool CPythonEncoder::EncodeMapData(LPBUFFER_WRAPPER pBuffer, PyObject *pyObj)
{
    WRAPPER_DATA_HEADER header;
    header.tp = DTP_MAP;
    header.size = 0;
    LPWRAPPER_DATA_HEADER pheader = NULL;
    if (pBuffer->alloc(sizeof(header)))
    {
        pheader = (LPWRAPPER_DATA_HEADER)pBuffer->curr;
        pBuffer->push(&header, sizeof(header));
        pBuffer->flush();
    }
    else
    {
        return false;
    }

    PyObject *k, *v;
    Py_ssize_t s = 0;
    WRAPPER_KEYVALUE_DATA_HEADER p;

    while(PyDict_Next(pyObj, &s, &k, &v) != 0)
    {
        p.ksize = 0;
        p.size = 0;

        void *pKeyData = pBuffer->curr + sizeof(p);

        if(PyInt_Check(k))
        {
            int nv = PyInt_AsLong(k);
            p.ktp = DTP_INT;
            p.ksize = sizeof(int);
            memcpy(pKeyData, &nv, p.ksize);
        }
        else if(PyFloat_Check(k))
        {
            double dv = PyFloat_AsDouble(k);
            p.ktp = DTP_DOUBLE;
            p.ksize = sizeof(double);
            memcpy(pKeyData, &dv, p.ksize);
        }
        else if(PyString_Check(k))
        {
            ENCODESTR szdata = PyString_AsString(k);
            p.ktp = DTP_STR;
            p.ksize = strlen(szdata) + 1;
            memcpy(pKeyData, szdata, p.ksize);
        }
        else
            continue;

        void *pData = ((char *)pKeyData) + p.ksize;

		if(PyBool_Check(v))
		{
			bool bv = (Py_True == v);
			p.tp = DTP_BOOL;
			p.size = sizeof(bool);
			memcpy(pData, &bv , sizeof(bool));
		}else if(PyInt_Check(v))
        {
            int nv = PyInt_AsLong(v);
            p.tp = DTP_INT;
            p.size = sizeof(int);
            memcpy(pData, &nv , sizeof(int));
        }
        else if(PyLong_Check(v))
        {
            INT64 n64v = PyLong_AsLongLong(v);
            p.tp = DTP_INT;
            p.size = sizeof(INT64);
            memcpy(pData, &n64v , sizeof(INT64));
        }
        else if(PyFloat_Check(v))
        {
            double dv = PyFloat_AsDouble(v);
            p.tp = DTP_DOUBLE;
            p.size = sizeof(double);
            memcpy(pData, &dv , sizeof(double));
        }
        else if(PyString_Check(v))
        {
            ENCODESTR szdata = PyString_AsString(v);
            p.tp = DTP_STR;
            p.size = strlen(szdata) + 1;
            memcpy(pData, szdata , p.size);
        }
        else if (PyDict_Check(v))
        {
            BUFFER_WRAPPER sb;
            unsigned char *pCurr = (unsigned char *)pData;
            if(pCurr >= pBuffer->end)
                continue;
            sb.init(pCurr, pBuffer->end - pCurr);
            EncodeMapData(&sb, v);
            p.tp = DTP_MAP;
            p.size = sb.data_size();
        }
        else if (PySequence_Check(v))
        {
            BUFFER_WRAPPER sb;
            unsigned char *pCurr = (unsigned char *)pData;
            if(pCurr >= pBuffer->end)
                continue;
            sb.init(pCurr, pBuffer->end - pCurr);
            EncodeListData(&sb, v);
            p.tp = DTP_LIST;
            p.size = sb.data_size();
        }

        if(p.size == 0)
        {
            continue;
        }
        unsigned int nTotalSize = sizeof(WRAPPER_KEYVALUE_DATA_HEADER) + p.ksize + p.size;
        if(pBuffer->alloc(nTotalSize))
        {
            pBuffer->push(&p, sizeof(WRAPPER_KEYVALUE_DATA_HEADER));
            pBuffer->flush();
            pBuffer->curr += (p.ksize + p.size);
            pheader->size ++;
        }
    }
    return true;
}

bool CPythonEncoder::EncodeListData(LPBUFFER_WRAPPER pBuffer, PyObject *pyObj)
{
    WRAPPER_DATA_HEADER header;
    header.tp = DTP_LIST;
    header.size = 0;
    LPWRAPPER_DATA_HEADER pheader = NULL;
    if (pBuffer->alloc(sizeof(header)))
    {
        pheader = (LPWRAPPER_DATA_HEADER)pBuffer->curr;
        pBuffer->push(&header, sizeof(header));
        pBuffer->flush();
    }
    else
    {
        return false;
    }

    Py_ssize_t se = PySequence_Size(pyObj);
    PyObject *v = NULL;
    for (int i = 0; i < se; i++)
	{
		PyEx_XDECREF(v);

        v = PySequence_GetItem(pyObj, i);
        WRAPPER_DATA_HEADER p;
        p.size = 0;
        void *pData = pBuffer->curr + sizeof(p);

        if(PyInt_Check(v))
        {
            int nv = PyInt_AsLong(v);
            p.tp = DTP_INT;
            p.size = sizeof(int);
            memcpy(pData, &nv , sizeof(int));
        }
        else if(PyLong_Check(v))
        {
            INT64 n64v = PyLong_AsLongLong(v);
            p.tp = DTP_INT;
            p.size = sizeof(INT64);
            memcpy(pData, &n64v , sizeof(INT64));
        }
        else if(PyBool_Check(v))
        {
            bool bv = Py_True == v;
            p.tp = DTP_BOOL;
            p.size = sizeof(bool);
            memcpy(pData, &bv , sizeof(bool));
        }
        else if(PyFloat_Check(v))
        {
            double dv = PyFloat_AsDouble(v);
            p.tp = DTP_DOUBLE;
            p.size = sizeof(double);
            memcpy(pData, &dv , sizeof(double));
        }
        else if(PyString_Check(v))
        {
            ENCODESTR szdata = PyString_AsString(v);
            p.tp = DTP_STR;
            p.size = strlen(szdata) + 1;
            memcpy(pData, szdata , p.size);
        }
        else if (PyDict_Check(v))
        {
            BUFFER_WRAPPER sb;
            unsigned char *pCurr = (unsigned char *)pData;
            if(pCurr >= pBuffer->end)
                continue;
            sb.init(pCurr, pBuffer->end - pCurr);
            EncodeMapData(&sb, v);
            p.tp = DTP_MAP;
            p.size = sb.data_size();
        }
        else if (PySequence_Check(v))
        {
            BUFFER_WRAPPER sb;
            unsigned char *pCurr = (unsigned char *)pData;
            if(pCurr >= pBuffer->end)
                continue;
            sb.init(pCurr, pBuffer->end - pCurr);
            EncodeListData(&sb, v);
            p.tp = DTP_LIST;
            p.size = sb.data_size();
        }
        if(p.size == 0)
        {
            continue;
        }
        unsigned int nTotalSize = sizeof(WRAPPER_DATA_HEADER) + p.size;
        if(pBuffer->alloc(nTotalSize))
        {
            pBuffer->push(&p, sizeof(WRAPPER_DATA_HEADER));
            pBuffer->flush();
            pBuffer->curr += (p.size);
            pheader->size ++;
        }
    }

	PyEx_XDECREF(v);
    return true;
}

LPBUFFER_WRAPPER CPythonEncoder::Encode(LPBUFFER_WRAPPER pDstBuffer, void *pSrc)
{
    PyObject *pyObj = (PyObject *)pSrc;
    if (PyDict_Check(pyObj))
    {
        EncodeMapData(pDstBuffer, pyObj);
    }
    else if(PyList_Check(pyObj))
    {
        EncodeListData(pDstBuffer, pyObj);
    }
    return pDstBuffer;
}

bool CPythonDecoder::DecodeMapData(LPWRAPPER_DATA_HEADER pHeader, LPBUFFER_WRAPPER pBuffer, PyObject *pyObj)
{
    int ncount = pHeader->size;
    int n = 0;
    LPWRAPPER_KEYVALUE_DATA_HEADER p = NULL;
    PyObject *k = NULL, *v = NULL;
    BUFFER_WRAPPER bw;

    while((n < ncount) && (p = (LPWRAPPER_KEYVALUE_DATA_HEADER)pBuffer->read(sizeof(WRAPPER_KEYVALUE_DATA_HEADER))))
    {
        void *pKeyData = ((char *)p) + sizeof(WRAPPER_KEYVALUE_DATA_HEADER);
        void *pData = ((char *)pKeyData) + p->ksize;

		unsigned int nskip_size = p->ksize + p->size;

        switch (p->ktp)
        {
        case DTP_STR:
        {
            k = Py_BuildValue("s",(ENCODESTR)pKeyData);
        }
        break;
        case DTP_INT:
        {
            k = Py_BuildValue("i",*(int *)pKeyData);
        }
        break;
        case DTP_FLOAT:
        {
            k = Py_BuildValue("f",*(float *)pKeyData);
        }
        break;
        case DTP_INT64:
        {
            k = Py_BuildValue("l",*(INT64 *)pKeyData);
        }
        break;
        default:
        {
			PyEx_XDECREF(k);
			PyEx_XDECREF(v);
            continue;
        }
        }
        switch (p->tp)
		{
		case DTP_NULL:
		{
			v = Py_None;
		}
		break;
        case DTP_STR:
        {
            v = Py_BuildValue("s",(ENCODESTR)pData);
        }
        break;
        case DTP_INT:
        {
            v = Py_BuildValue("i",*(int *)pData);
        }
        break;
        case DTP_BOOL:
        {
			v = (*(bool *)pData) ? Py_True : Py_False;
        }
        break;
        case DTP_FLOAT:
        {
            v = Py_BuildValue("f",*(float *)pData);
        }
        break;
        case DTP_INT64:
        {
            v = Py_BuildValue("l",*(INT64 *)pData);
        }
        break;
        case DTP_DOUBLE:
        {
            v = Py_BuildValue("f",*(double *)pData);
        }
        break;
        case DTP_MAP:
        case DTP_LIST:
        case DTP_BUFFER:
        case DTP_PBUFFER:
        {
            bw.readonly_ref(pData, p->size);
            LPWRAPPER_DATA_HEADER psh = (LPWRAPPER_DATA_HEADER)bw.read(sizeof(WRAPPER_DATA_HEADER));
            switch (psh->tp)
            {
            case DTP_MAP:
            {
                v = PyDict_New();
                DecodeMapData(psh, &bw, v);
            }
            break;
            case DTP_LIST:
            {
                v = PyList_New(0);
                DecodeListData(psh, &bw, v);
            }
            break;
			}
			//nskip_size = sizeof(WRAPPER_DATA_HEADER);
        }
        break;
        default:
        {
			PyEx_XDECREF(k);
			PyEx_XDECREF(v);
            continue;
        }
        }
        if(!v || !k)
		{
			PyEx_XDECREF(k);
			PyEx_XDECREF(v);
            continue;
        }

		PyDict_SetItem(pyObj, k, v);
		PyEx_XDECREF(k);
		PyEx_XDECREF(v);

        if(!pBuffer->skip(nskip_size))
        {
            DEBUG_INFO_CODEC("data buffer is invalid!");
            break;
        }
        n++;
    }
    return true;
}

bool CPythonDecoder::DecodeListData(LPWRAPPER_DATA_HEADER pHeader, LPBUFFER_WRAPPER pBuffer, PyObject *pyObj)
{
    int ncount = pHeader->size;
    int n = 0;
    LPWRAPPER_DATA_HEADER p = NULL;
    PyObject *v = NULL;
    BUFFER_WRAPPER bw;

    while((n < ncount) && (p = (LPWRAPPER_DATA_HEADER)pBuffer->read(sizeof(WRAPPER_DATA_HEADER))))
    {
        void *pData = ((char *)p) + sizeof(WRAPPER_DATA_HEADER);

        switch (p->tp)
		{
		case DTP_NULL:
		{
			v = Py_None;
		}
		break;
        case DTP_STR:
        {
            v = Py_BuildValue("s",(ENCODESTR)pData);
        }
        break;
        case DTP_INT:
        {
            v = Py_BuildValue("i",*(int *)pData);
        }
        break;
        case DTP_BOOL:
		{
			v = (*(bool *)pData) ? Py_True : Py_False;
        }
        break;
        case DTP_FLOAT:
        {
            v = Py_BuildValue("f",*(float *)pData);
        }
        break;
        case DTP_INT64:
        {
            v = Py_BuildValue("l",*(INT64 *)pData);
        }
        break;
        case DTP_DOUBLE:
        {
            v = Py_BuildValue("f",*(double *)pData);
        }
        break;
        case DTP_MAP:
        case DTP_LIST:
        case DTP_BUFFER:
        case DTP_PBUFFER:
        {
            bw.readonly_ref(pData, p->size);
            LPWRAPPER_DATA_HEADER psh = (LPWRAPPER_DATA_HEADER)bw.read(sizeof(WRAPPER_DATA_HEADER));
            switch (psh->tp)
            {
            case DTP_MAP:
            {
                v = PyDict_New();
                DecodeMapData(psh, &bw, v);
            }
            break;
            case DTP_LIST:
            {
                v = PyList_New(0);
                DecodeListData(psh, &bw, v);
            }
            break;
            }
        }
        break;
        default:
			{
				PyEx_XDECREF(v);
				continue;
			}
        }

        if(!v)
        {
            continue;
        }

		PyList_Append(pyObj, v);
		PyEx_XDECREF(v);

        if(!pBuffer->skip(p->size))
        {
            DEBUG_INFO_CODEC("data buffer is invalid!");
            break;
        }
        n++;
    }
    return true;
}

void *CPythonDecoder::Decode(LPBUFFER_WRAPPER pSrcBuffer, void *pDst)
{
    PyObject *pyObj = (PyObject *)pDst;
    LPWRAPPER_DATA_HEADER pheader = (LPWRAPPER_DATA_HEADER)pSrcBuffer->read(sizeof(WRAPPER_DATA_HEADER));
	if(!pheader)
		return pyObj;
    switch (pheader->tp)
    {
    case DTP_MAP:
    {
    	if (!pyObj)
    	{
			pyObj = PyDict_New();
			Py_XINCREF(pyObj);
    	}
		if(pyObj && PyDict_Check(pyObj))
            DecodeMapData(pheader, pSrcBuffer, pyObj);
        break;
    }
    case DTP_LIST:
    {
    	if (!pyObj)
    	{
			pyObj = PyList_New(0);
			Py_XINCREF(pyObj);
    	}
		if(pyObj && PyList_Check(pyObj))
            DecodeListData(pheader, pSrcBuffer, pyObj);
        break;
    }
    }
    return pyObj;
}
